#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
容器环境检测与逃逸辅助工具
支持Docker和Kubernetes环境识别与权限扫描
注意：仅提供检测与信息收集功能，避免直接集成逃逸EXP引发合规风险
"""

import os
import sys
import json
import time
import subprocess
import re
from pathlib import Path

class ContainerEnvironmentDetector:
    """容器环境检测器"""
    
    def __init__(self):
        self.is_container = False
        self.container_type = None
        self.container_info = {}
        self.escape_vectors = []
        self.security_context = {}
    
    def detect_container_environment(self):
        """检测容器环境"""
        print("[*] 开始检测容器环境...")
        
        # 检测是否在容器中
        if self._check_container_indicators():
            self.is_container = True
            print("[+] 检测到容器环境")
            
            # 识别容器类型
            self._identify_container_type()
            
            # 收集容器信息
            self._collect_container_info()
            
            # 分析逃逸向量
            self._analyze_escape_vectors()
            
        else:
            print("[-] 未检测到容器环境")
        
        return self.is_container
    
    def _check_container_indicators(self):
        """检查容器环境指标"""
        indicators = []
        
        # 检查 /.dockerenv 文件
        if os.path.exists('/.dockerenv'):
            indicators.append('dockerenv_file')
            print("[+] 发现 /.dockerenv 文件")
        
        # 检查 /proc/1/cgroup
        if self._check_cgroup_indicators():
            indicators.append('cgroup_indicators')
        
        # 检查进程树
        if self._check_process_tree():
            indicators.append('process_tree')
        
        # 检查文件系统
        if self._check_filesystem_indicators():
            indicators.append('filesystem')
        
        # 检查网络命名空间
        if self._check_network_namespace():
            indicators.append('network_namespace')
        
        return len(indicators) > 0
    
    def _check_cgroup_indicators(self):
        """检查cgroup指标"""
        try:
            with open('/proc/1/cgroup', 'r') as f:
                cgroup_content = f.read()
            
            container_patterns = [
                r'/docker/',
                r'/kubepods/',
                r'/lxc/',
                r'/system\.slice/docker',
                r'/containerd/'
            ]
            
            for pattern in container_patterns:
                if re.search(pattern, cgroup_content):
                    print(f"[+] cgroup中发现容器指标: {pattern}")
                    return True
            
        except (IOError, OSError):
            pass
        
        return False
    
    def _check_process_tree(self):
        """检查进程树"""
        try:
            # 检查PID 1的进程名
            with open('/proc/1/comm', 'r') as f:
                init_process = f.read().strip()
            
            container_init_processes = [
                'dockerd', 'containerd', 'runc', 'docker-init',
                'tini', 'dumb-init', 'systemd'
            ]
            
            if init_process in container_init_processes:
                print(f"[+] 检测到容器初始化进程: {init_process}")
                return True
                
        except (IOError, OSError):
            pass
        
        return False
    
    def _check_filesystem_indicators(self):
        """检查文件系统指标"""
        container_fs_indicators = [
            '/var/lib/docker',
            '/run/docker.sock',
            '/var/run/docker.sock',
            '/proc/self/mountinfo'
        ]
        
        for indicator in container_fs_indicators:
            if os.path.exists(indicator):
                print(f"[+] 发现容器文件系统指标: {indicator}")
                return True
        
        # 检查挂载信息
        try:
            with open('/proc/self/mountinfo', 'r') as f:
                mount_info = f.read()
            
            if 'docker' in mount_info or 'overlay' in mount_info:
                print("[+] 挂载信息显示容器环境")
                return True
                
        except (IOError, OSError):
            pass
        
        return False
    
    def _check_network_namespace(self):
        """检查网络命名空间"""
        try:
            # 比较网络命名空间
            host_ns = os.readlink('/proc/1/ns/net')
            current_ns = os.readlink('/proc/self/ns/net')
            
            if host_ns != current_ns:
                print("[+] 检测到独立的网络命名空间")
                return True
                
        except (IOError, OSError):
            pass
        
        return False
    
    def _identify_container_type(self):
        """识别容器类型"""
        # 检查Docker
        if os.path.exists('/.dockerenv') or self._check_docker_indicators():
            self.container_type = 'docker'
            print("[+] 识别为Docker容器")
        
        # 检查Kubernetes
        elif self._check_kubernetes_indicators():
            self.container_type = 'kubernetes'
            print("[+] 识别为Kubernetes Pod")
        
        # 检查LXC
        elif self._check_lxc_indicators():
            self.container_type = 'lxc'
            print("[+] 识别为LXC容器")
        
        else:
            self.container_type = 'unknown'
            print("[?] 未知容器类型")
    
    def _check_docker_indicators(self):
        """检查Docker指标"""
        docker_indicators = [
            '/var/run/docker.sock',
            '/proc/self/cgroup'
        ]
        
        for indicator in docker_indicators:
            if os.path.exists(indicator):
                try:
                    with open(indicator, 'r') as f:
                        content = f.read()
                    if 'docker' in content:
                        return True
                except:
                    continue
        
        return False
    
    def _check_kubernetes_indicators(self):
        """检查Kubernetes指标"""
        k8s_indicators = [
            '/var/run/secrets/kubernetes.io',
            '/run/secrets/kubernetes.io/serviceaccount'
        ]
        
        # 检查Kubernetes服务账户
        for indicator in k8s_indicators:
            if os.path.exists(indicator):
                return True
        
        # 检查环境变量
        k8s_env_vars = [
            'KUBERNETES_SERVICE_HOST',
            'KUBERNETES_SERVICE_PORT',
            'KUBERNETES_PORT'
        ]
        
        for env_var in k8s_env_vars:
            if os.getenv(env_var):
                return True
        
        return False
    
    def _check_lxc_indicators(self):
        """检查LXC指标"""
        try:
            with open('/proc/1/environ', 'r') as f:
                environ = f.read()
            
            if 'container=lxc' in environ:
                return True
                
        except (IOError, OSError):
            pass
        
        return False
    
    def _collect_container_info(self):
        """收集容器信息"""
        print("[*] 收集容器详细信息...")
        
        # 基础信息
        self.container_info['hostname'] = os.uname().nodename
        self.container_info['container_type'] = self.container_type
        
        # 收集特定类型信息
        if self.container_type == 'docker':
            self._collect_docker_info()
        elif self.container_type == 'kubernetes':
            self._collect_kubernetes_info()
        elif self.container_type == 'lxc':
            self._collect_lxc_info()
        
        # 收集权限信息
        self._collect_privilege_info()
        
        # 收集挂载信息
        self._collect_mount_info()
        
        # 收集网络信息
        self._collect_network_info()
    
    def _collect_docker_info(self):
        """收集Docker信息"""
        docker_info = {}
        
        # 检查Docker socket
        docker_sockets = [
            '/var/run/docker.sock',
            '/run/docker.sock'
        ]
        
        for socket_path in docker_sockets:
            if os.path.exists(socket_path):
                docker_info['docker_socket'] = socket_path
                docker_info['socket_accessible'] = os.access(socket_path, os.R_OK | os.W_OK)
                break
        
        # 检查Docker环境变量
        docker_env_vars = ['DOCKER_HOST', 'DOCKER_TLS_VERIFY', 'DOCKER_CERT_PATH']
        for env_var in docker_env_vars:
            value = os.getenv(env_var)
            if value:
                docker_info[env_var.lower()] = value
        
        self.container_info['docker'] = docker_info
    
    def _collect_kubernetes_info(self):
        """收集Kubernetes信息"""
        k8s_info = {}
        
        # 服务账户信息
        sa_path = '/var/run/secrets/kubernetes.io/serviceaccount'
        if os.path.exists(sa_path):
            k8s_info['service_account_path'] = sa_path
            
            # 读取token
            token_file = os.path.join(sa_path, 'token')
            if os.path.exists(token_file):
                try:
                    with open(token_file, 'r') as f:
                        k8s_info['has_token'] = True
                        k8s_info['token_length'] = len(f.read())
                except:
                    k8s_info['has_token'] = False
            
            # 读取namespace
            namespace_file = os.path.join(sa_path, 'namespace')
            if os.path.exists(namespace_file):
                try:
                    with open(namespace_file, 'r') as f:
                        k8s_info['namespace'] = f.read().strip()
                except:
                    pass
        
        # Kubernetes环境变量
        k8s_env_vars = [
            'KUBERNETES_SERVICE_HOST', 'KUBERNETES_SERVICE_PORT',
            'KUBERNETES_PORT', 'KUBERNETES_PORT_443_TCP'
        ]
        
        for env_var in k8s_env_vars:
            value = os.getenv(env_var)
            if value:
                k8s_info[env_var.lower()] = value
        
        self.container_info['kubernetes'] = k8s_info
    
    def _collect_lxc_info(self):
        """收集LXC信息"""
        lxc_info = {}
        
        # LXC配置路径
        lxc_config_paths = [
            '/proc/1/root',
            '/sys/fs/cgroup'
        ]
        
        for path in lxc_config_paths:
            if os.path.exists(path):
                lxc_info[f'path_{path.replace("/", "_")}'] = True
        
        self.container_info['lxc'] = lxc_info
    
    def _collect_privilege_info(self):
        """收集权限信息"""
        privilege_info = {}
        
        # 检查用户权限
        privilege_info['uid'] = os.getuid()
        privilege_info['gid'] = os.getgid()
        privilege_info['euid'] = os.geteuid()
        privilege_info['egid'] = os.getegid()
        
        # 检查capabilities
        privilege_info['capabilities'] = self._get_capabilities()
        
        # 检查特权模式
        privilege_info['privileged'] = self._check_privileged_mode()
        
        # 检查安全上下文
        privilege_info['security_context'] = self._get_security_context()
        
        self.container_info['privileges'] = privilege_info
    
    def _get_capabilities(self):
        """获取capabilities"""
        capabilities = {}
        
        try:
            with open('/proc/self/status', 'r') as f:
                for line in f:
                    if line.startswith('Cap'):
                        key, value = line.strip().split(':', 1)
                        capabilities[key.lower()] = value.strip()
        except:
            pass
        
        return capabilities
    
    def _check_privileged_mode(self):
        """检查特权模式"""
        # 检查是否有完整的capabilities
        try:
            with open('/proc/self/status', 'r') as f:
                content = f.read()
            
            # 查找CapEff行
            cap_eff_match = re.search(r'CapEff:\s+([0-9a-f]+)', content)
            if cap_eff_match:
                cap_eff = cap_eff_match.group(1)
                # 如果所有capabilities都被设置，可能是特权模式
                if cap_eff == 'ffffffffffffffff' or cap_eff == '3fffffffff':
                    return True
        except:
            pass
        
        return False
    
    def _get_security_context(self):
        """获取安全上下文"""
        security_context = {}
        
        # 检查SELinux
        if os.path.exists('/proc/self/attr/current'):
            try:
                with open('/proc/self/attr/current', 'r') as f:
                    security_context['selinux'] = f.read().strip()
            except:
                pass
        
        # 检查AppArmor
        if os.path.exists('/proc/self/attr/apparmor/current'):
            try:
                with open('/proc/self/attr/apparmor/current', 'r') as f:
                    security_context['apparmor'] = f.read().strip()
            except:
                pass
        
        return security_context
    
    def _collect_mount_info(self):
        """收集挂载信息"""
        mount_info = {}
        
        try:
            with open('/proc/self/mountinfo', 'r') as f:
                mounts = f.readlines()
            
            sensitive_mounts = []
            for mount in mounts:
                # 检查敏感挂载点
                if any(path in mount for path in ['/proc', '/sys', '/dev', '/var/run/docker.sock']):
                    sensitive_mounts.append(mount.strip())
            
            mount_info['sensitive_mounts'] = sensitive_mounts
            mount_info['total_mounts'] = len(mounts)
            
        except:
            pass
        
        self.container_info['mounts'] = mount_info
    
    def _collect_network_info(self):
        """收集网络信息"""
        network_info = {}
        
        # 检查网络接口
        try:
            result = subprocess.run(['ip', 'addr'], capture_output=True, text=True)
            if result.returncode == 0:
                network_info['interfaces'] = result.stdout
        except:
            pass
        
        # 检查路由表
        try:
            result = subprocess.run(['ip', 'route'], capture_output=True, text=True)
            if result.returncode == 0:
                network_info['routes'] = result.stdout
        except:
            pass
        
        self.container_info['network'] = network_info
    
    def _analyze_escape_vectors(self):
        """分析逃逸向量"""
        print("[*] 分析潜在逃逸向量...")
        
        # Docker逃逸向量
        if self.container_type == 'docker':
            self._analyze_docker_escape_vectors()
        
        # Kubernetes逃逸向量
        elif self.container_type == 'kubernetes':
            self._analyze_k8s_escape_vectors()
        
        # 通用逃逸向量
        self._analyze_common_escape_vectors()
    
    def _analyze_docker_escape_vectors(self):
        """分析Docker逃逸向量"""
        docker_info = self.container_info.get('docker', {})
        privilege_info = self.container_info.get('privileges', {})
        
        # 检查Docker socket访问
        if docker_info.get('socket_accessible'):
            self.escape_vectors.append({
                'type': 'docker_socket',
                'description': 'Docker socket可访问，可能允许容器逃逸',
                'risk_level': 'high',
                'vector': 'Docker API调用'
            })
        
        # 检查特权模式
        if privilege_info.get('privileged'):
            self.escape_vectors.append({
                'type': 'privileged_mode',
                'description': '容器运行在特权模式',
                'risk_level': 'critical',
                'vector': '直接访问主机资源'
            })
        
        # 检查敏感挂载
        mount_info = self.container_info.get('mounts', {})
        sensitive_mounts = mount_info.get('sensitive_mounts', [])
        
        for mount in sensitive_mounts:
            if '/var/run/docker.sock' in mount:
                self.escape_vectors.append({
                    'type': 'docker_socket_mount',
                    'description': 'Docker socket被挂载到容器',
                    'risk_level': 'critical',
                    'vector': 'Docker socket挂载'
                })
            elif '/proc' in mount and 'rw' in mount:
                self.escape_vectors.append({
                    'type': 'proc_mount',
                    'description': '/proc以读写模式挂载',
                    'risk_level': 'high',
                    'vector': '进程文件系统访问'
                })
    
    def _analyze_k8s_escape_vectors(self):
        """分析Kubernetes逃逸向量"""
        k8s_info = self.container_info.get('kubernetes', {})
        
        # 检查服务账户权限
        if k8s_info.get('has_token'):
            self.escape_vectors.append({
                'type': 'service_account',
                'description': '存在Kubernetes服务账户token',
                'risk_level': 'medium',
                'vector': 'Kubernetes API访问'
            })
        
        # 检查主机网络
        network_info = self.container_info.get('network', {})
        if 'host' in str(network_info):
            self.escape_vectors.append({
                'type': 'host_network',
                'description': '使用主机网络模式',
                'risk_level': 'high',
                'vector': '网络命名空间共享'
            })
    
    def _analyze_common_escape_vectors(self):
        """分析通用逃逸向量"""
        privilege_info = self.container_info.get('privileges', {})
        capabilities = privilege_info.get('capabilities', {})
        
        # 检查危险capabilities
        dangerous_caps = ['CAP_SYS_ADMIN', 'CAP_SYS_PTRACE', 'CAP_SYS_MODULE']
        
        for cap in dangerous_caps:
            if cap.lower() in str(capabilities).lower():
                self.escape_vectors.append({
                    'type': 'dangerous_capability',
                    'description': f'检测到危险capability: {cap}',
                    'risk_level': 'high',
                    'vector': f'{cap}权限利用'
                })
    
    def get_escape_recommendations(self):
        """获取逃逸建议（仅用于防御）"""
        recommendations = []
        
        if not self.escape_vectors:
            recommendations.append("未发现明显的逃逸向量")
            return recommendations
        
        recommendations.append("检测到以下潜在逃逸向量，建议加强防护：")
        
        for vector in self.escape_vectors:
            risk_level = vector['risk_level']
            description = vector['description']
            
            if risk_level == 'critical':
                recommendations.append(f"🔴 严重: {description}")
            elif risk_level == 'high':
                recommendations.append(f"🟠 高危: {description}")
            elif risk_level == 'medium':
                recommendations.append(f"🟡 中危: {description}")
            else:
                recommendations.append(f"🔵 低危: {description}")
        
        # 防护建议
        recommendations.extend([
            "",
            "防护建议:",
            "- 避免使用特权模式运行容器",
            "- 不要挂载敏感路径到容器",
            "- 限制容器的capabilities",
            "- 使用安全上下文约束",
            "- 定期更新容器运行时"
        ])
        
        return recommendations
    
    def generate_container_report(self):
        """生成容器检测报告"""
        report = {
            'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'),
            'is_container': self.is_container,
            'container_type': self.container_type,
            'container_info': self.container_info,
            'escape_vectors': self.escape_vectors,
            'recommendations': self.get_escape_recommendations()
        }
        
        return report
    
    def print_detection_summary(self):
        """打印检测摘要"""
        print("\n" + "="*60)
        print("容器环境检测报告")
        print("="*60)
        
        if self.is_container:
            print(f"[+] 容器类型: {self.container_type.upper()}")
            
            # 打印基础信息
            if self.container_info:
                print(f"[+] 主机名: {self.container_info.get('hostname', 'Unknown')}")
                
                privilege_info = self.container_info.get('privileges', {})
                print(f"[+] 用户ID: {privilege_info.get('uid', 'Unknown')}")
                print(f"[+] 特权模式: {'是' if privilege_info.get('privileged') else '否'}")
            
            # 打印逃逸向量
            if self.escape_vectors:
                print(f"\n[!] 发现 {len(self.escape_vectors)} 个潜在逃逸向量:")
                for i, vector in enumerate(self.escape_vectors, 1):
                    risk_color = {
                        'critical': '🔴',
                        'high': '🟠', 
                        'medium': '🟡',
                        'low': '🔵'
                    }.get(vector['risk_level'], '⚪')
                    
                    print(f"    {i}. {risk_color} {vector['description']}")
            else:
                print("[+] 未发现明显的逃逸向量")
            
            # 打印建议
            recommendations = self.get_escape_recommendations()
            if recommendations:
                print(f"\n[*] 安全建议:")
                for rec in recommendations:
                    if rec:  # 跳过空行
                        print(f"    {rec}")
        else:
            print("[-] 未检测到容器环境")
        
        print("="*60)


class KubernetesPermissionScanner:
    """Kubernetes权限扫描器"""
    
    def __init__(self):
        self.service_account_path = '/var/run/secrets/kubernetes.io/serviceaccount'
        self.permissions = {}
        self.api_server = None
        self.token = None
        self.namespace = None
    
    def scan_permissions(self):
        """扫描Kubernetes权限"""
        print("[*] 开始扫描Kubernetes权限...")
        
        if not self._load_service_account():
            print("[-] 无法加载服务账户信息")
            return False
        
        # 获取API服务器地址
        self._get_api_server()
        
        # 扫描权限
        self._scan_api_permissions()
        
        return True
    
    def _load_service_account(self):
        """加载服务账户信息"""
        if not os.path.exists(self.service_account_path):
            return False
        
        # 读取token
        token_file = os.path.join(self.service_account_path, 'token')
        if os.path.exists(token_file):
            try:
                with open(token_file, 'r') as f:
                    self.token = f.read().strip()
            except:
                return False
        
        # 读取namespace
        namespace_file = os.path.join(self.service_account_path, 'namespace')
        if os.path.exists(namespace_file):
            try:
                with open(namespace_file, 'r') as f:
                    self.namespace = f.read().strip()
            except:
                self.namespace = 'default'
        
        return bool(self.token)
    
    def _get_api_server(self):
        """获取API服务器地址"""
        self.api_server = os.getenv('KUBERNETES_SERVICE_HOST')
        if not self.api_server:
            self.api_server = 'kubernetes.default.svc'
    
    def _scan_api_permissions(self):
        """扫描API权限"""
        # 模拟kubectl auth can-i检查
        permissions_to_check = [
            ('get', 'pods'),
            ('list', 'pods'),
            ('create', 'pods'),
            ('delete', 'pods'),
            ('get', 'secrets'),
            ('list', 'secrets'),
            ('get', 'nodes'),
            ('list', 'nodes'),
            ('create', 'pods/exec'),
            ('get', 'serviceaccounts'),
            ('list', 'serviceaccounts')
        ]
        
        print("[*] 检查API权限...")
        
        for verb, resource in permissions_to_check:
            # 这里只是模拟检查，实际环境中需要调用Kubernetes API
            permission_key = f"{verb}_{resource}"
            self.permissions[permission_key] = self._simulate_permission_check(verb, resource)
            
            if self.permissions[permission_key]:
                print(f"[+] 有权限: {verb} {resource}")
            else:
                print(f"[-] 无权限: {verb} {resource}")
    
    def _simulate_permission_check(self, verb, resource):
        """模拟权限检查"""
        # 在实际环境中，这里应该调用Kubernetes API进行权限检查
        # 这里只是返回随机结果用于演示
        import random
        return random.choice([True, False])
    
    def get_permission_summary(self):
        """获取权限摘要"""
        if not self.permissions:
            return "未扫描权限"
        
        granted = sum(1 for p in self.permissions.values() if p)
        total = len(self.permissions)
        
        return f"权限扫描结果: {granted}/{total} 个权限被授予"


def main():
    """主函数"""
    print("容器环境检测与逃逸辅助工具")
    print("="*50)
    
    # 创建检测器
    detector = ContainerEnvironmentDetector()
    
    # 检测容器环境
    is_container = detector.detect_container_environment()
    
    if is_container:
        # 如果是Kubernetes环境，扫描权限
        if detector.container_type == 'kubernetes':
            print("\n[*] 检测到Kubernetes环境，开始权限扫描...")
            k8s_scanner = KubernetesPermissionScanner()
            k8s_scanner.scan_permissions()
            
            # 添加权限信息到报告
            detector.container_info['k8s_permissions'] = k8s_scanner.permissions
    
    # 打印检测结果
    detector.print_detection_summary()
    
    # 生成报告
    report = detector.generate_container_report()
    
    # 保存报告
    report_file = f"container_detection_report.json"
    with open(report_file, 'w', encoding='utf-8') as f:
        json.dump(report, f, ensure_ascii=False, indent=2)
    print(f"\n[+] 检测报告已保存: {report_file}")


if __name__ == "__main__":
    main()